package com.rslai.base.tool.servertest;

import com.rslai.base.tool.servertest.annotation.Filter;
import com.rslai.base.tool.servertest.annotation.Interceptor;
import com.rslai.base.tool.servertest.casefilter.CaseFilter;
import com.rslai.base.tool.servertest.casereader.DatacaseReader;
import com.rslai.base.tool.servertest.casereader.Dom4jCaseReader;
import com.rslai.base.tool.servertest.casereader.GlobalVariablesReader;
import com.rslai.base.tool.servertest.context.Context;
import com.rslai.base.tool.servertest.dsl.DSLCommandReader;
import com.rslai.base.tool.servertest.intercept.InterceptorFactory;
import com.rslai.base.tool.servertest.intercept.StepCommandInterceptor;
import com.rslai.base.tool.servertest.model.CaseStatistics;
import com.rslai.base.tool.servertest.model.DataSuite;
import com.rslai.base.tool.servertest.model.Environment;
import com.rslai.base.tool.servertest.model.Operation;
import com.rslai.base.tool.servertest.model.SvnInfo;
import com.rslai.base.tool.servertest.model.TestCase;
import com.rslai.base.tool.servertest.model.TestSuite;
import com.rslai.base.tool.servertest.paramfilter.FilterFactory;
import com.rslai.base.tool.servertest.paramfilter.ParamFilter;
import com.rslai.base.tool.servertest.reporter.QJSONReporter;
import com.rslai.base.tool.servertest.reporter.Reporter;
import com.rslai.base.tool.servertest.transport.command.ServiceFactory;
import com.rslai.base.tool.servertest.util.IpUtil;
import com.rslai.base.tool.servertest.util.PropertyUtils;
import com.rslai.base.tool.servertest.util.ReflectionUtils;
import com.rslai.commons.validator.jsonvalidator.factories.ValidatorFactory;
import com.rslai.commons.validator.jsonvalidator.validators.Validator;

import java.io.FileNotFoundException;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.DocumentException;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;

public class ServerTest extends ParentRunner<TestSuiteRunner> {
    private static final Context GLOBALCONTEXT = new Context(null);

    private List<TestSuiteRunner> children = new ArrayList();
    private Reporter qjsonReporter;
    private final CaseFilter filter;
    private ServertestOptions options;

    public ServerTest(Class<?> testClass)
            throws InitializationError, DocumentException, FileNotFoundException {
        super(testClass);
        this.options = new ServertestOptions(testClass);

        addJobAndIdToContext(this.options);
        MonitorLog.start(this.options);
        setArrayValidateMethod();

        List files = this.options.testCases();
        List dataFiles = this.options.dataCases();
        ensureHasCases(files, dataFiles);
        List beforeFiles = this.options.before();
        List afterFiles = this.options.after();

        this.qjsonReporter = this.options.reporter();

        CaseStatistics caseStatistics = ((QJSONReporter) this.qjsonReporter).getCaseStatistics();
        caseStatistics.setJob(this.options.jobName());
        caseStatistics.setBuild(this.options.buildNumber());

        SvnInfo svnInfo = new SvnInfoReader().read();
        this.qjsonReporter.addSvnInfo(svnInfo);

        determinedLocalHost();

        attatchHandlers(testClass);
        attatchInterceptors(testClass);

        List suites = null;
        if (CollectionUtils.isNotEmpty(dataFiles)) {
            List expectLevels = this.options.levels();
            List expectStatuss = this.options.statuss();

            DatacaseReader datacaseReader = new DatacaseReader();
            suites = datacaseReader.getSuites(dataFiles, this.options.keyFile(), this.options.dslFile());
            datacaseReader.processDataSuite(suites, expectLevels, expectStatuss);
        }

        new DSLCommandReader().read(this.options.dslFile(), this.qjsonReporter);

        ServiceFactory.getInstance().init(this.options.serviceConfig(), this.qjsonReporter);
        Environment.initEnvironment(testClass);

        List dataList = null;
        if (StringUtils.isNotBlank(this.options.global())) {
            Map globalVariables = new GlobalVariablesReader().parse(this.options.global());
            addGlobalParametersToContext((Map) globalVariables.get("set"));
            dataList = (List) globalVariables.get("data");
        }
        Set extraTagSet = getExtraTag(dataList);
        this.filter = this.options.createCaseFilter(extraTagSet);

        addChildren(beforeFiles, null, null);
        addChildren(files, suites, dataList);
        addChildren(afterFiles, null, null);
    }

    private Set<String> getExtraTag(List<Map<String, Object>> dataList) {
        if (dataList == null) {
            return Collections.EMPTY_SET;
        }
        Set extraTagSet = new HashSet();
        for (Map map : dataList) {
            if (map.get("site") != null) {
                extraTagSet.add((String) map.get("site"));
            }
        }
        return extraTagSet;
    }

    private void addGlobalParametersToContext(Map<String, Object> setParameters) {
        if (setParameters == null) return;
        Iterator iterator = setParameters.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            GLOBALCONTEXT.addContext((String) entry.getKey(), entry.getValue());
        }
    }

    private void addJobAndIdToContext(ServertestOptions options) {
        if ((StringUtils.isNotBlank(options.jobName())) && (StringUtils.isNotBlank(options.jobName()))) {
            GLOBALCONTEXT.addContext("job", options.jobName());
            GLOBALCONTEXT.addContext("build", options.buildNumber());
        }
    }

    private void setArrayValidateMethod() {
        // 设置数组对比方式，有序，无序
        String property = PropertyUtils.getProperty("array_default_order_validate", "true"); // 读取配置参数，默认有序对比
        com.rslai.commons.validator.jsonvalidator.JsonValidator.arrayDefaultOrderValidate = Boolean.valueOf(property);
    }

    private void ensureHasCases(List<String> files, List<String> dataFiles) {
        if (((files == null) || (files.size() == 0)) && ((dataFiles == null) || (dataFiles.size() == 0))) {
            throw new RuntimeException("未找到测试用例文件，请检查files指定的case文件是否存在。");
        }
    }

    private void determinedLocalHost() {
        GLOBALCONTEXT.addContext("jenkins.host", IpUtil.getLocalNetworkAddress());
    }

    private void attatchInterceptors(Class<?> testClass) {
        if (!testClass.isAnnotationPresent(Interceptor.class)) return;
        Interceptor interceptor = (Interceptor) testClass.getAnnotation(Interceptor.class);
        Class[] value = interceptor.value();
        for (Class interceptorClass : value) {
            StepCommandInterceptor stepCommandInterceptor = (StepCommandInterceptor) ReflectionUtils.newInstance(interceptorClass);
            InterceptorFactory.registerInterceptor(stepCommandInterceptor);
        }
    }

    private void attatchHandlers(Class<?> testClass) {
        Field[] fields = testClass.getDeclaredFields();
        for (Field field : fields)
            if (isFilter(field)) {
                ParamFilter filter = (ParamFilter) ReflectionUtils.newInstance(field.getType());
                FilterFactory.register(filter);
            }
    }

    private boolean isFilter(Field field) {
        return (field.isAnnotationPresent(Filter.class)) && (ParamFilter.class.isAssignableFrom(field.getType()));
    }

    protected List<TestSuiteRunner> getChildren() {
        return this.children;
    }

    protected Description describeChild(TestSuiteRunner child) {
        return child.getDescription();
    }

    protected void runChild(TestSuiteRunner child, RunNotifier notifier) {
        child.run(notifier);
    }

    public void run(RunNotifier notifier) {
        try {
            super.run(notifier);
        } finally {
            this.qjsonReporter.close();
        }
    }

    private void addChildren(List<String> files, List<DataSuite> dataSuites, List<Map<String, Object>> dataList) throws InitializationError, DocumentException, FileNotFoundException {
        List suites = new ArrayList(files.size());
        for (String file : files) {
            TestSuite testSuite = new Dom4jCaseReader().readTestCase(file);
            if (testSuite != null) {
                this.filter.filter(testSuite);
                if (!testSuite.getTestCases().isEmpty())
                    suites.add(testSuite);
            }
        }
        if (dataSuites != null) {
            suites.addAll(new DatacaseReader().convertDataSuiteToTestSuite(dataSuites, this.options.ids()));
        }
        Collections.sort(suites);
        addTestSuiteRunner(suites, dataList);
    }

    private void addTestSuiteRunner(List<TestSuite> suites, List<Map<String, Object>> dataList)
            throws InitializationError {
        Iterator i$;
        if (dataList == null)
            for (TestSuite suite : suites) {
                ((QJSONReporter) this.qjsonReporter).getCaseStatistics().addRunSum(statictisCase(suite));
                Context suitContext = new Context(GLOBALCONTEXT);
                this.children.add(new TestSuiteRunner(getTestClass().getJavaClass(), suite, suitContext, this.qjsonReporter));
            }
        else
            for (i$ = dataList.iterator(); i$.hasNext(); ) {
                Map data = (Map) i$.next();
                for (TestSuite suite : suites) {
                    ((QJSONReporter) this.qjsonReporter).getCaseStatistics().addRunSum(statictisCase(suite));
                    Context suitContext = new Context(GLOBALCONTEXT);
                    String param = addSuiteParametersToContext(suitContext, data);
                    TestSuite cloneSuite = suite.clone();
                    modifyTestSuite(cloneSuite, param);
                    this.children.add(new TestSuiteRunner(getTestClass().getJavaClass(), cloneSuite, suitContext, this.qjsonReporter));
                }
            }
        Map data;
    }

    private String addSuiteParametersToContext(Context suitContext, Map<String, Object> data) {
        Iterator iterator = data.entrySet().iterator();
        List paramList = new ArrayList();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            paramList.add(entry.getKey() + "=" + entry.getValue());
            suitContext.addContext((String) entry.getKey(), entry.getValue());
        }
        return StringUtils.join(paramList, "_");
    }

    private void modifyTestSuite(TestSuite testSuite, String param) {
        testSuite.setId((new StringBuilder()).append(testSuite.getId()).append("_").append(param).toString());
        if (!testSuite.getDesc().contains(param))
            testSuite.setDesc((new StringBuilder()).append(testSuite.getDesc()).append("_").append(param).toString());
        List testCaseList = testSuite.getTestCases();
        if (testCaseList != null) {
            TestCase testCase;
            for (Iterator i$ = testCaseList.iterator(); i$.hasNext(); testCase.setDesc((new StringBuilder()).append(testCase.getDesc()).append("_").append(param).toString()))
                testCase = (TestCase) i$.next();

        }
    }

    private int statictisCase(TestSuite suite) {
        List testCases = suite.getTestCases();
        if (testCases == null) {
            return 0;
        }
        return testCases.size();
    }

    public static void registerValidator(String validatorName, Class<? extends Validator> validatorClass) {
        ValidatorFactory.registerValidator(validatorName, validatorClass);
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({java.lang.annotation.ElementType.TYPE})
    @Documented
    @Inherited
    public static @interface Options {
        public abstract String[] testcase() default {""};

        public abstract String[] before() default {""};

        public abstract String[] after() default {""};

        public abstract String[] tags() default {"*"};

        public abstract String[] levels() default {"*"};

        public abstract String[] statuss() default {"*"};

        public abstract String ids() default "";

        public abstract String[] service() default {"service.xml"};

        public abstract String global() default "";

        public abstract String[] dsl() default {""};

        public abstract String[] dataFiles() default {""};

        public abstract String keyFile() default "cases/key.xml";

        public abstract Operation operation() default Operation.CLEAR_INSERT;
    }
}
